Compare commits

...

31 Commits

Author SHA1 Message Date
Daniel Valdivia
c741e9ccae Release v0.16.2 (#1945)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-05-03 11:33:42 -07:00
jinapurapu
ab835286b0 Add Service Account Policy restriction improvement (#1921) 2022-05-03 11:03:57 -07:00
Alex
6485718a97 Changed Share modal styles (#1942)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-05-02 22:30:48 -05:00
adfost
00bcb54b67 Describe pod API (#1861) 2022-05-02 18:35:36 -07:00
Alex
fef7863810 Adjusted edit / delete tags modal styles (#1939) 2022-05-02 17:01:34 -07:00
jinapurapu
427b9b4892 Integration test for PolicyNameContainsSpace (#1940) 2022-05-02 17:35:01 -05:00
jinapurapu
34adc5451d Disabled Save button on Add Policy Screen if policy name contains space (#1937) 2022-05-02 15:08:11 -07:00
adfost
224e8d4bba More groups tests (#1923) 2022-05-02 11:25:21 -05:00
Prakash Senthil Vel
07d75e19d5 UX KBar Style (#1935) 2022-05-02 11:04:07 -05:00
Daniel Valdivia
31871f54d4 Update HelpBox for Create Bucket. Add spacing. (#1934) 2022-04-30 12:00:58 -07:00
Alex
6069991405 Improvements for download / upload manager (#1933)
- Changed styles on progress bars & items
- Fixed some issues in error state & handling
- Added cancel capability to objects
- Added visual indicators when new objects are added to pool

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-04-29 20:54:12 -07:00
Daniel Valdivia
a017c71d20 Release v0.16.1 (#1928)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-04-29 18:55:43 -07:00
Harshavardhana
4001f14953 keep lookupMap for easy reading for relevant publicKey file (#1930)
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-04-29 15:14:04 -07:00
Cesar Celis Hernandez
130413cbef having idp configured via env variable only (#1931) 2022-04-29 14:48:21 -07:00
Lenin Alevski
0622cc658b fix: parseTenantCertificates was ignoring cert-manager secrets (#1929)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>

Co-authored-by: Cesar Celis Hernandez <celis.hernandez.cesar@gmail.com>
2022-04-29 11:55:07 -07:00
Alex
663a5b196d Remove of suspended traffic warning in profile page (#1926) 2022-04-29 01:45:42 -05:00
Prakash Senthil Vel
6f676f73a4 UX Prometheus dashboard (#1907) 2022-04-28 19:57:16 -07:00
Daniel Valdivia
26d7001ae1 Remove all un-used CSS classes (#1924)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-04-28 19:33:46 -05:00
Daniel Valdivia
f79a8e8177 FormLayout Component (#1922)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-04-28 15:13:20 -07:00
Daniel Valdivia
0e5147bb1d Constraint PageLayout to max 1220px on large resolutions (#1919)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-04-28 14:34:29 -07:00
Lenin Alevski
566fb27fc1 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>
2022-04-28 12:55:06 -07:00
Lenin Alevski
8c18829089 Update DNS Rebinding etcd vulnerable dependency (#1918)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-04-28 14:21:17 -05:00
adfost
c68c175827 committing new tests (#1879) 2022-04-28 11:52:37 -05:00
dependabot[bot]
415088ae2d Bump ejs from 3.1.6 to 3.1.7 in /portal-ui (#1915)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.7)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 11:00:34 -05:00
adfost
158926f192 groups_test (#1916) 2022-04-28 08:09:06 -07:00
Lenin Alevski
f026ffffc8 Fix profiling endpoint and adding tests (#1917)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-04-27 20:07:32 -07:00
Cesar Celis Hernandez
bd0edea3df Increase coverage threshold (#1914) 2022-04-27 16:49:58 -05:00
Alex
07b4dad4d3 Fixed issues with Quota modal (#1911)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-04-27 14:08:19 -07:00
Cesar Celis Hernandez
0df796bc03 fixing integration tests to use proxy (#1912) 2022-04-27 12:33:10 -07:00
Daniel Valdivia
cf0212391e Console Swagger Module Reorganization (#1881)
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
2022-04-27 11:45:04 -07:00
Alex
cb3a695c25 Removed quota from versioning section & added inside summary page (#1910)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-04-27 09:36:39 -07:00
1124 changed files with 17621 additions and 8060 deletions

View File

@@ -65,7 +65,7 @@ function main() {
check_tenant_status tenant-lite storage-lite
kubectl -n minio-operator port-forward svc/console 9090 &
kubectl proxy &
}
main "$@"

View File

@@ -1346,7 +1346,7 @@ jobs:
result=${result%\%}
echo "result:"
echo $result
threshold=36.6
threshold=41.2
if (( $(echo "$result >= $threshold" |bc -l) )); then
echo "It is equal or greater than threshold, passed!"
else

1
.gitignore vendored
View File

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

View File

@@ -164,18 +164,12 @@ test-sso-integration:
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_IDENTITY_OPENID_CLIENT_SECRET=0nfJuqIt0iPnRIUJkvetve5l38C6gi9W \
-e MINIO_IDENTITY_OPENID_CONFIG_URL=http://keycloak-container:8080/auth/realms/myrealm/.well-known/openid-configuration \
-e MINIO_IDENTITY_OPENID_CLIENT_ID="account" \
-e MINIO_ROOT_USER=minio \
-e MINIO_ROOT_PASSWORD=minio123 $(MINIO_VERSION) server /data{1...4} --address :9000 --console-address :9001)
@(sleep 60)
@echo "run mc commands"
@(docker run --name minio-client --network my-net -dit --entrypoint=/bin/sh minio/mc)
@(docker exec minio-client mc alias set myminio/ http://minio:9000 minio minio123)
@(docker exec minio-client mc admin config set myminio identity_openid config_url="http://keycloak-container:8080/auth/realms/myrealm/.well-known/openid-configuration" client_id="account")
@(docker exec minio-client mc admin service restart myminio)
@echo "starting bash script"
@(env bash $(PWD)/sso-integration/set-sso.sh)
@echo "install jq"
@(sudo apt install jq)
@echo "Executing the test:"
@(cd sso-integration && go test -coverpkg=../restapi -c -tags testrunmain . && mkdir -p coverage && ./sso-integration.test -test.v -test.run "^Test*" -test.coverprofile=coverage/sso-system.out)

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

861
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
// 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 integration
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_ConfigAPI(t *testing.T) {
assert := assert.New(t)
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Config - Valid",
args: args{
api: "/configs",
},
expectedStatus: 200,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}

365
integration/groups_test.go Normal file
View File

@@ -0,0 +1,365 @@
// 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 integration
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_AddGroupAPI(t *testing.T) {
assert := assert.New(t)
AddUser("member1", "testtest", []string{}, []string{"consoleAdmin"})
type args struct {
group string
members []string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Create Group - Valid",
args: args{
group: "test",
members: []string{"member1"},
},
expectedStatus: 201,
expectedError: nil,
},
{
name: "Create Group - Invalid",
args: args{
group: "test",
members: []string{},
},
expectedStatus: 400,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataPolicy["group"] = tt.args.group
requestDataPolicy["members"] = tt.args.members
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"POST", "http://localhost:9090/api/v1/groups", requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, "Status Code is incorrect")
}
})
}
}
func Test_GetGroupAPI(t *testing.T) {
assert := assert.New(t)
AddUser("member2", "testtest", []string{}, []string{"consoleAdmin"})
AddGroup("getgroup1", []string{"member2"})
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Get Group - Valid",
args: args{
api: "?name=getgroup1",
},
expectedStatus: 200,
expectedError: nil,
},
{
name: "Get Group - Invalid",
args: args{
api: "?name=askfjalkd",
},
expectedStatus: 500,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1/group%s", tt.args.api), requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, "Status Code is incorrect")
}
})
}
}
func Test_ListGroupsAPI(t *testing.T) {
assert := assert.New(t)
tests := []struct {
name string
expectedStatus int
expectedError error
}{
{
name: "Get Group - Valid",
expectedStatus: 200,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"GET", "http://localhost:9090/api/v1/groups", requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, "Status Code is incorrect")
}
})
}
}
func Test_PutGroupsAPI(t *testing.T) {
assert := assert.New(t)
AddUser("member3", "testtest", []string{}, []string{"consoleAdmin"})
AddGroup("putgroup1", []string{})
type args struct {
api string
members []string
status string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Put Group - Valid",
args: args{
api: "?name=putgroup1",
members: []string{"member3"},
status: "enabled",
},
expectedStatus: 200,
expectedError: nil,
},
{
name: "Put Group - Invalid",
args: args{
api: "?name=gdgfdfgd",
members: []string{"member3"},
status: "enabled",
},
expectedStatus: 500,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataPolicy["members"] = tt.args.members
requestDataPolicy["status"] = tt.args.status
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT", fmt.Sprintf("http://localhost:9090/api/v1/group%s", tt.args.api), requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, "Status Code is incorrect")
}
})
}
}
func Test_DeleteGroupAPI(t *testing.T) {
assert := assert.New(t)
AddGroup("grouptests1", []string{})
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
verb string
}{
{
name: "Delete Group - Valid",
args: args{
api: "?name=grouptests1",
},
verb: "DELETE",
expectedStatus: 204,
expectedError: nil,
},
{
name: "Delete Group - Invalid",
args: args{
api: "?name=grouptests12345",
},
verb: "DELETE",
expectedStatus: 404,
expectedError: nil,
},
{
name: "Access Group After Delete - Invalid",
args: args{
api: "?name=grouptests1",
},
verb: "GET",
expectedStatus: 500,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
tt.verb, fmt.Sprintf("http://localhost:9090/api/v1/group%s", tt.args.api), requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, "Status Code is incorrect")
}
})
}
}

View File

@@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"testing"
@@ -30,7 +31,64 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_PolicyAPI(t *testing.T) {
func AddPolicy(name string, definition string) (*http.Response, error) {
/*
This is an atomic function to add user and can be reused across
different functions.
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataAdd := map[string]interface{}{
"name": name,
"policy": definition,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"POST", "http://localhost:9090/api/v1/policies", requestDataBody)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func SetPolicy(policies []string, entityName string, entityType string) (*http.Response, error) {
/*
This is an atomic function to add user and can be reused across
different functions.
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataAdd := map[string]interface{}{
"name": policies,
"entityType": entityType,
"entityName": entityName,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT", "http://localhost:9090/api/v1/set-policy", requestDataBody)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func Test_AddPolicyAPI(t *testing.T) {
assert := assert.New(t)
type args struct {
@@ -69,11 +127,12 @@ func Test_PolicyAPI(t *testing.T) {
expectedStatus: 201,
expectedError: nil,
},
{
name: "Create Policy - Invalid",
args: args{
api: "/policies",
name: "test",
name: "test2",
policy: swag.String(`
{
"Version": "2012-10-17",
@@ -94,6 +153,31 @@ func Test_PolicyAPI(t *testing.T) {
expectedStatus: 500,
expectedError: nil,
},
{
name: "Create Policy - Space in Name",
args: args{
api: "/policies",
name: "space test",
policy: swag.String(`
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`),
},
expectedStatus: 400,
expectedError: nil,
},
}
for _, tt := range tests {
@@ -103,11 +187,9 @@ func Test_PolicyAPI(t *testing.T) {
Timeout: 3 * time.Second,
}
// Add policy
requestDataPolicy := map[string]interface{}{}
requestDataPolicy["name"] = tt.args.name
if tt.args.policy != nil {
requestDataPolicy["name"] = tt.args.name
requestDataPolicy["policy"] = *tt.args.policy
}
@@ -127,10 +209,604 @@ func Test_PolicyAPI(t *testing.T) {
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, "Status Code is incorrect")
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}
func Test_SetPolicyAPI(t *testing.T) {
assert := assert.New(t)
AddUser("policyuser1", "testtest", []string{}, []string{"readwrite"})
AddGroup("testgroup123", []string{})
AddPolicy("setpolicytest", `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
type args struct {
api string
entityType string
entityName string
policyName []string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Set Policy - Valid",
args: args{
api: "/set-policy",
policyName: []string{"setpolicytest"},
entityType: "user",
entityName: "policyuser1",
},
expectedStatus: 204,
expectedError: nil,
},
{
name: "Set Policy - Invalid",
args: args{
api: "/set-policy",
policyName: []string{"test3"},
entityType: "user",
entityName: "policyuser1",
},
expectedStatus: 500,
expectedError: nil,
},
{
name: "Set Policy Group - Valid",
args: args{
api: "/set-policy",
policyName: []string{"setpolicytest"},
entityType: "group",
entityName: "testgroup123",
},
expectedStatus: 204,
expectedError: nil,
},
{
name: "Set Policy Group - Invalid",
args: args{
api: "/set-policy",
policyName: []string{"test3"},
entityType: "group",
entityName: "testgroup123",
},
expectedStatus: 500,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataPolicy["entityName"] = tt.args.entityName
requestDataPolicy["entityType"] = tt.args.entityType
if tt.args.policyName != nil {
requestDataPolicy["name"] = tt.args.policyName
}
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}
func Test_SetPolicyMultipleAPI(t *testing.T) {
assert := assert.New(t)
AddUser("policyuser2", "testtest", []string{}, []string{"readwrite"})
AddUser("policyuser3", "testtest", []string{}, []string{"readwrite"})
AddGroup("testgroup1234", []string{})
AddPolicy("setpolicytest2", `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
type args struct {
api string
users []string
groups []string
name []string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Set Policy - Valid",
args: args{
api: "/set-policy-multi",
name: []string{"setpolicytest2"},
users: []string{"policyuser2", "policyuser3"},
},
expectedStatus: 204,
expectedError: nil,
},
{
name: "Set Policy - Invalid",
args: args{
api: "/set-policy-multi",
name: []string{"test3"},
users: []string{"policyuser2", "policyuser3"},
},
expectedStatus: 500,
expectedError: nil,
},
{
name: "Set Policy Group - Valid",
args: args{
api: "/set-policy-multi",
name: []string{"setpolicytest2"},
groups: []string{"testgroup1234"},
},
expectedStatus: 204,
expectedError: nil,
},
{
name: "Set Policy Group - Valid",
args: args{
api: "/set-policy-multi",
name: []string{"setpolicytest23"},
groups: []string{"testgroup1234"},
},
expectedStatus: 500,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataPolicy := map[string]interface{}{}
requestDataPolicy["name"] = tt.args.name
requestDataPolicy["users"] = tt.args.users
requestDataPolicy["groups"] = tt.args.groups
requestDataJSON, _ := json.Marshal(requestDataPolicy)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}
func Test_ListPoliciesAPI(t *testing.T) {
assert := assert.New(t)
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "List Policies",
args: args{
api: "/policies",
},
expectedStatus: 200,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}
func Test_GetPolicyAPI(t *testing.T) {
assert := assert.New(t)
AddPolicy("getpolicytest", `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Get Policies - Invalid",
args: args{
api: "/policy?name=test3",
},
expectedStatus: 500,
expectedError: nil,
},
{
name: "Get Policies - Valid",
args: args{
api: "/policy?name=getpolicytest",
},
expectedStatus: 200,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}
func Test_PolicyListUsersAPI(t *testing.T) {
assert := assert.New(t)
AddUser("policyuser4", "testtest", []string{}, []string{"readwrite"})
AddPolicy("policylistusers", `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
SetPolicy([]string{"policylistusers"}, "policyuser4", "user")
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "List Users for Policy - Valid",
args: args{
api: "/policies/policylistusers/users",
},
expectedStatus: 200,
expectedError: nil,
},
{
name: "List Users for Policy - Invalid",
args: args{
api: "/policies/test2/users",
},
expectedStatus: 404,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
bodyBytes, _ := ioutil.ReadAll(response.Body)
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
if response.StatusCode == 200 {
assert.Equal("[\"policyuser4\"]\n", string(bodyBytes))
}
}
})
}
}
func Test_PolicyListGroupsAPI(t *testing.T) {
assert := assert.New(t)
AddGroup("testgroup12345", []string{})
AddPolicy("policylistgroups", `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
SetPolicy([]string{"policylistgroups"}, "testgroup12345", "group")
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "List Users for Policy - Valid",
args: args{
api: "/policies/policylistgroups/groups",
},
expectedStatus: 200,
expectedError: nil,
},
{
name: "List Users for Policy - Invalid",
args: args{
api: "/policies/test3/groups",
},
expectedStatus: 404,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
bodyBytes, _ := ioutil.ReadAll(response.Body)
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
if response.StatusCode == 200 {
assert.Equal("[\"testgroup12345\"]\n", string(bodyBytes))
}
}
})
}
}
func Test_DeletePolicyAPI(t *testing.T) {
assert := assert.New(t)
AddPolicy("testdelete", `
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
type args struct {
api string
method string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Delete Policies - Valid",
args: args{
api: "/policy?name=testdelete",
method: "DELETE",
},
expectedStatus: 204,
expectedError: nil,
},
{
name: "Get Policy After Delete - Invalid",
args: args{
api: "/policy?name=testdelete",
method: "GET",
},
expectedStatus: 500,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
tt.args.method, fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}

View File

@@ -0,0 +1,121 @@
// 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 integration
import (
"archive/zip"
"bytes"
"fmt"
"io"
"log"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestStartProfiling(t *testing.T) {
testAsser := assert.New(t)
tests := []struct {
name string
}{
{
name: "start/stop profiling",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
files := map[string]bool{
"profile-127.0.0.1:9000-goroutines.txt": false,
"profile-127.0.0.1:9000-goroutines-before.txt": false,
"profile-127.0.0.1:9000-goroutines-before,debug=2.txt": false,
"profile-127.0.0.1:9000-threads-before.pprof": false,
"profile-127.0.0.1:9000-mem.pprof": false,
"profile-127.0.0.1:9000-threads.pprof": false,
"profile-127.0.0.1:9000-cpu.pprof": false,
"profile-127.0.0.1:9000-mem-before.pprof": false,
"profile-127.0.0.1:9000-block.pprof": false,
"profile-127.0.0.1:9000-trace.trace": false,
"profile-127.0.0.1:9000-mutex.pprof": false,
"profile-127.0.0.1:9000-mutex-before.pprof": false,
}
client := &http.Client{
Timeout: 3 * time.Second,
}
destination := "/api/v1/profiling/start"
finalURL := fmt.Sprintf("http://localhost:9090%s", destination)
request, err := http.NewRequest("POST", finalURL, strings.NewReader("{\"type\":\"cpu,mem,block,mutex,trace,threads,goroutines\"}"))
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
testAsser.Nil(err, fmt.Sprintf("%s returned an error: %v", tt.name, err))
testAsser.Equal(201, response.StatusCode)
time.Sleep(5 * time.Second)
destination = "/api/v1/profiling/stop"
finalURL = fmt.Sprintf("http://localhost:9090%s", destination)
request, err = http.NewRequest("POST", finalURL, nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err = client.Do(request)
testAsser.Nil(err, fmt.Sprintf("%s returned an error: %v", tt.name, err))
testAsser.Equal(200, response.StatusCode)
zipFileBytes, err := io.ReadAll(response.Body)
if err != nil {
testAsser.Nil(err, fmt.Sprintf("%s returned an error: %v", tt.name, err))
}
filetype := http.DetectContentType(zipFileBytes)
testAsser.Equal("application/zip", filetype)
zipReader, err := zip.NewReader(bytes.NewReader(zipFileBytes), int64(len(zipFileBytes)))
if err != nil {
testAsser.Nil(err, fmt.Sprintf("%s returned an error: %v", tt.name, err))
}
// Read all the files from zip archive
for _, zipFile := range zipReader.File {
files[zipFile.Name] = true
}
for k, v := range files {
testAsser.Equal(true, v, fmt.Sprintf("%s : compressed file expected to have %v file inside", tt.name, k))
}
})
}
}

View File

@@ -854,3 +854,68 @@ func TestUsersGroupsBulk(t *testing.T) {
}
}
func Test_GetUserPolicyAPI(t *testing.T) {
assert := assert.New(t)
// 1. Create an active user with valid policy
var groups = []string{}
var policies = []string{"readwrite"}
addUserResponse, addUserError := AddUser(
"getpolicyuser", "secretKey", groups, policies)
if addUserError != nil {
log.Println(addUserError)
return
}
if addUserResponse != nil {
fmt.Println("StatusCode:", addUserResponse.StatusCode)
assert.Equal(
201, addUserResponse.StatusCode, "Status Code is incorrect")
}
type args struct {
api string
}
tests := []struct {
name string
args args
expectedStatus int
expectedError error
}{
{
name: "Get User Policies",
args: args{
api: "/user/policy",
},
expectedStatus: 200,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
if response != nil {
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
}
})
}
}

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: 'minio/console:v0.16.0'
image: 'minio/console:v0.16.2'
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -32,7 +32,7 @@ spec:
spec:
containers:
- name: console
image: 'minio/console:v0.16.0'
image: 'minio/console:v0.16.2'
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_MINIO_SERVER

70
models/condition.go Normal file
View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Condition condition
//
// swagger:model condition
type Condition struct {
// status
Status string `json:"status,omitempty"`
// type
Type string `json:"type,omitempty"`
}
// Validate validates this condition
func (m *Condition) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this condition based on context it is used
func (m *Condition) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Condition) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Condition) UnmarshalBinary(b []byte) error {
var res Condition
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

70
models/config_map.go Normal file
View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ConfigMap config map
//
// swagger:model configMap
type ConfigMap struct {
// name
Name string `json:"name,omitempty"`
// optional
Optional bool `json:"optional,omitempty"`
}
// Validate validates this config map
func (m *ConfigMap) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this config map based on context it is used
func (m *ConfigMap) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ConfigMap) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ConfigMap) UnmarshalBinary(b []byte) error {
var res ConfigMap
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

309
models/container.go Normal file
View File

@@ -0,0 +1,309 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Container container
//
// swagger:model container
type Container struct {
// args
Args []string `json:"args"`
// container ID
ContainerID string `json:"containerID,omitempty"`
// environment variables
EnvironmentVariables []*EnvironmentVariable `json:"environmentVariables"`
// host ports
HostPorts []string `json:"hostPorts"`
// image
Image string `json:"image,omitempty"`
// image ID
ImageID string `json:"imageID,omitempty"`
// last state
LastState *State `json:"lastState,omitempty"`
// mounts
Mounts []*Mount `json:"mounts"`
// name
Name string `json:"name,omitempty"`
// ports
Ports []string `json:"ports"`
// ready
Ready bool `json:"ready,omitempty"`
// restart count
RestartCount int64 `json:"restartCount,omitempty"`
// state
State *State `json:"state,omitempty"`
}
// Validate validates this container
func (m *Container) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateEnvironmentVariables(formats); err != nil {
res = append(res, err)
}
if err := m.validateLastState(formats); err != nil {
res = append(res, err)
}
if err := m.validateMounts(formats); err != nil {
res = append(res, err)
}
if err := m.validateState(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Container) validateEnvironmentVariables(formats strfmt.Registry) error {
if swag.IsZero(m.EnvironmentVariables) { // not required
return nil
}
for i := 0; i < len(m.EnvironmentVariables); i++ {
if swag.IsZero(m.EnvironmentVariables[i]) { // not required
continue
}
if m.EnvironmentVariables[i] != nil {
if err := m.EnvironmentVariables[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("environmentVariables" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("environmentVariables" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Container) validateLastState(formats strfmt.Registry) error {
if swag.IsZero(m.LastState) { // not required
return nil
}
if m.LastState != nil {
if err := m.LastState.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("lastState")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("lastState")
}
return err
}
}
return nil
}
func (m *Container) validateMounts(formats strfmt.Registry) error {
if swag.IsZero(m.Mounts) { // not required
return nil
}
for i := 0; i < len(m.Mounts); i++ {
if swag.IsZero(m.Mounts[i]) { // not required
continue
}
if m.Mounts[i] != nil {
if err := m.Mounts[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("mounts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("mounts" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Container) validateState(formats strfmt.Registry) error {
if swag.IsZero(m.State) { // not required
return nil
}
if m.State != nil {
if err := m.State.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("state")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("state")
}
return err
}
}
return nil
}
// ContextValidate validate this container based on the context it is used
func (m *Container) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateEnvironmentVariables(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateLastState(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateMounts(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateState(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Container) contextValidateEnvironmentVariables(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.EnvironmentVariables); i++ {
if m.EnvironmentVariables[i] != nil {
if err := m.EnvironmentVariables[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("environmentVariables" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("environmentVariables" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Container) contextValidateLastState(ctx context.Context, formats strfmt.Registry) error {
if m.LastState != nil {
if err := m.LastState.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("lastState")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("lastState")
}
return err
}
}
return nil
}
func (m *Container) contextValidateMounts(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Mounts); i++ {
if m.Mounts[i] != nil {
if err := m.Mounts[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("mounts" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("mounts" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *Container) contextValidateState(ctx context.Context, formats strfmt.Registry) error {
if m.State != nil {
if err := m.State.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("state")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("state")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Container) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Container) UnmarshalBinary(b []byte) error {
var res Container
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,517 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// DescribePodWrapper describe pod wrapper
//
// swagger:model describePodWrapper
type DescribePodWrapper struct {
// annotations
Annotations []*Annotation `json:"annotations"`
// conditions
Conditions []*Condition `json:"conditions"`
// containers
Containers []*Container `json:"containers"`
// controller ref
ControllerRef string `json:"controllerRef,omitempty"`
// deletion grace period seconds
DeletionGracePeriodSeconds int64 `json:"deletionGracePeriodSeconds,omitempty"`
// deletion timestamp
DeletionTimestamp string `json:"deletionTimestamp,omitempty"`
// labels
Labels []*Label `json:"labels"`
// message
Message string `json:"message,omitempty"`
// name
Name string `json:"name,omitempty"`
// namespace
Namespace string `json:"namespace,omitempty"`
// node name
NodeName string `json:"nodeName,omitempty"`
// node selector
NodeSelector []*NodeSelector `json:"nodeSelector"`
// phase
Phase string `json:"phase,omitempty"`
// pod IP
PodIP string `json:"podIP,omitempty"`
// priority
Priority int64 `json:"priority,omitempty"`
// priority class name
PriorityClassName string `json:"priorityClassName,omitempty"`
// qos class
QosClass string `json:"qosClass,omitempty"`
// reason
Reason string `json:"reason,omitempty"`
// start time
StartTime string `json:"startTime,omitempty"`
// tolerations
Tolerations []*Toleration `json:"tolerations"`
// volumes
Volumes []*Volume `json:"volumes"`
}
// Validate validates this describe pod wrapper
func (m *DescribePodWrapper) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAnnotations(formats); err != nil {
res = append(res, err)
}
if err := m.validateConditions(formats); err != nil {
res = append(res, err)
}
if err := m.validateContainers(formats); err != nil {
res = append(res, err)
}
if err := m.validateLabels(formats); err != nil {
res = append(res, err)
}
if err := m.validateNodeSelector(formats); err != nil {
res = append(res, err)
}
if err := m.validateTolerations(formats); err != nil {
res = append(res, err)
}
if err := m.validateVolumes(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *DescribePodWrapper) validateAnnotations(formats strfmt.Registry) error {
if swag.IsZero(m.Annotations) { // not required
return nil
}
for i := 0; i < len(m.Annotations); i++ {
if swag.IsZero(m.Annotations[i]) { // not required
continue
}
if m.Annotations[i] != nil {
if err := m.Annotations[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("annotations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) validateConditions(formats strfmt.Registry) error {
if swag.IsZero(m.Conditions) { // not required
return nil
}
for i := 0; i < len(m.Conditions); i++ {
if swag.IsZero(m.Conditions[i]) { // not required
continue
}
if m.Conditions[i] != nil {
if err := m.Conditions[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("conditions" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("conditions" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) validateContainers(formats strfmt.Registry) error {
if swag.IsZero(m.Containers) { // not required
return nil
}
for i := 0; i < len(m.Containers); i++ {
if swag.IsZero(m.Containers[i]) { // not required
continue
}
if m.Containers[i] != nil {
if err := m.Containers[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("containers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("containers" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) validateLabels(formats strfmt.Registry) error {
if swag.IsZero(m.Labels) { // not required
return nil
}
for i := 0; i < len(m.Labels); i++ {
if swag.IsZero(m.Labels[i]) { // not required
continue
}
if m.Labels[i] != nil {
if err := m.Labels[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("labels" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) validateNodeSelector(formats strfmt.Registry) error {
if swag.IsZero(m.NodeSelector) { // not required
return nil
}
for i := 0; i < len(m.NodeSelector); i++ {
if swag.IsZero(m.NodeSelector[i]) { // not required
continue
}
if m.NodeSelector[i] != nil {
if err := m.NodeSelector[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("nodeSelector" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("nodeSelector" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) validateTolerations(formats strfmt.Registry) error {
if swag.IsZero(m.Tolerations) { // not required
return nil
}
for i := 0; i < len(m.Tolerations); i++ {
if swag.IsZero(m.Tolerations[i]) { // not required
continue
}
if m.Tolerations[i] != nil {
if err := m.Tolerations[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tolerations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("tolerations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) validateVolumes(formats strfmt.Registry) error {
if swag.IsZero(m.Volumes) { // not required
return nil
}
for i := 0; i < len(m.Volumes); i++ {
if swag.IsZero(m.Volumes[i]) { // not required
continue
}
if m.Volumes[i] != nil {
if err := m.Volumes[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("volumes" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("volumes" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this describe pod wrapper based on the context it is used
func (m *DescribePodWrapper) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateAnnotations(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateConditions(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateContainers(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateLabels(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateNodeSelector(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateTolerations(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateVolumes(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *DescribePodWrapper) contextValidateAnnotations(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Annotations); i++ {
if m.Annotations[i] != nil {
if err := m.Annotations[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("annotations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("annotations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) contextValidateConditions(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Conditions); i++ {
if m.Conditions[i] != nil {
if err := m.Conditions[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("conditions" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("conditions" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) contextValidateContainers(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Containers); i++ {
if m.Containers[i] != nil {
if err := m.Containers[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("containers" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("containers" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) contextValidateLabels(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Labels); i++ {
if m.Labels[i] != nil {
if err := m.Labels[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("labels" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("labels" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) contextValidateNodeSelector(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.NodeSelector); i++ {
if m.NodeSelector[i] != nil {
if err := m.NodeSelector[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("nodeSelector" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("nodeSelector" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) contextValidateTolerations(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Tolerations); i++ {
if m.Tolerations[i] != nil {
if err := m.Tolerations[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tolerations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("tolerations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *DescribePodWrapper) contextValidateVolumes(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Volumes); i++ {
if m.Volumes[i] != nil {
if err := m.Volumes[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("volumes" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("volumes" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *DescribePodWrapper) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *DescribePodWrapper) UnmarshalBinary(b []byte) error {
var res DescribePodWrapper
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// EnvironmentVariable environment variable
//
// swagger:model environmentVariable
type EnvironmentVariable struct {
// key
Key string `json:"key,omitempty"`
// value
Value string `json:"value,omitempty"`
}
// Validate validates this environment variable
func (m *EnvironmentVariable) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this environment variable based on context it is used
func (m *EnvironmentVariable) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *EnvironmentVariable) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *EnvironmentVariable) UnmarshalBinary(b []byte) error {
var res EnvironmentVariable
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

76
models/mount.go Normal file
View File

@@ -0,0 +1,76 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Mount mount
//
// swagger:model mount
type Mount struct {
// mount path
MountPath string `json:"mountPath,omitempty"`
// name
Name string `json:"name,omitempty"`
// read only
ReadOnly bool `json:"readOnly,omitempty"`
// sub path
SubPath string `json:"subPath,omitempty"`
}
// Validate validates this mount
func (m *Mount) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this mount based on context it is used
func (m *Mount) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Mount) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Mount) UnmarshalBinary(b []byte) error {
var res Mount
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

133
models/projected_volume.go Normal file
View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ProjectedVolume projected volume
//
// swagger:model projectedVolume
type ProjectedVolume struct {
// sources
Sources []*ProjectedVolumeSource `json:"sources"`
}
// Validate validates this projected volume
func (m *ProjectedVolume) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateSources(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ProjectedVolume) validateSources(formats strfmt.Registry) error {
if swag.IsZero(m.Sources) { // not required
return nil
}
for i := 0; i < len(m.Sources); i++ {
if swag.IsZero(m.Sources[i]) { // not required
continue
}
if m.Sources[i] != nil {
if err := m.Sources[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("sources" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("sources" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this projected volume based on the context it is used
func (m *ProjectedVolume) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateSources(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ProjectedVolume) contextValidateSources(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Sources); i++ {
if m.Sources[i] != nil {
if err := m.Sources[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("sources" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("sources" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ProjectedVolume) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ProjectedVolume) UnmarshalBinary(b []byte) error {
var res ProjectedVolume
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,216 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ProjectedVolumeSource projected volume source
//
// swagger:model projectedVolumeSource
type ProjectedVolumeSource struct {
// config map
ConfigMap *ConfigMap `json:"configMap,omitempty"`
// downward Api
DownwardAPI bool `json:"downwardApi,omitempty"`
// secret
Secret *Secret `json:"secret,omitempty"`
// service account token
ServiceAccountToken *ServiceAccountToken `json:"serviceAccountToken,omitempty"`
}
// Validate validates this projected volume source
func (m *ProjectedVolumeSource) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateConfigMap(formats); err != nil {
res = append(res, err)
}
if err := m.validateSecret(formats); err != nil {
res = append(res, err)
}
if err := m.validateServiceAccountToken(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ProjectedVolumeSource) validateConfigMap(formats strfmt.Registry) error {
if swag.IsZero(m.ConfigMap) { // not required
return nil
}
if m.ConfigMap != nil {
if err := m.ConfigMap.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("configMap")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("configMap")
}
return err
}
}
return nil
}
func (m *ProjectedVolumeSource) validateSecret(formats strfmt.Registry) error {
if swag.IsZero(m.Secret) { // not required
return nil
}
if m.Secret != nil {
if err := m.Secret.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("secret")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("secret")
}
return err
}
}
return nil
}
func (m *ProjectedVolumeSource) validateServiceAccountToken(formats strfmt.Registry) error {
if swag.IsZero(m.ServiceAccountToken) { // not required
return nil
}
if m.ServiceAccountToken != nil {
if err := m.ServiceAccountToken.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("serviceAccountToken")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("serviceAccountToken")
}
return err
}
}
return nil
}
// ContextValidate validate this projected volume source based on the context it is used
func (m *ProjectedVolumeSource) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateConfigMap(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateSecret(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateServiceAccountToken(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *ProjectedVolumeSource) contextValidateConfigMap(ctx context.Context, formats strfmt.Registry) error {
if m.ConfigMap != nil {
if err := m.ConfigMap.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("configMap")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("configMap")
}
return err
}
}
return nil
}
func (m *ProjectedVolumeSource) contextValidateSecret(ctx context.Context, formats strfmt.Registry) error {
if m.Secret != nil {
if err := m.Secret.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("secret")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("secret")
}
return err
}
}
return nil
}
func (m *ProjectedVolumeSource) contextValidateServiceAccountToken(ctx context.Context, formats strfmt.Registry) error {
if m.ServiceAccountToken != nil {
if err := m.ServiceAccountToken.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("serviceAccountToken")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("serviceAccountToken")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *ProjectedVolumeSource) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ProjectedVolumeSource) UnmarshalBinary(b []byte) error {
var res ProjectedVolumeSource
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

70
models/pvc.go Normal file
View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Pvc pvc
//
// swagger:model pvc
type Pvc struct {
// claim name
ClaimName string `json:"claimName,omitempty"`
// read only
ReadOnly bool `json:"readOnly,omitempty"`
}
// Validate validates this pvc
func (m *Pvc) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this pvc based on context it is used
func (m *Pvc) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Pvc) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Pvc) UnmarshalBinary(b []byte) error {
var res Pvc
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

70
models/secret.go Normal file
View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Secret secret
//
// swagger:model secret
type Secret struct {
// name
Name string `json:"name,omitempty"`
// optional
Optional bool `json:"optional,omitempty"`
}
// Validate validates this secret
func (m *Secret) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this secret based on context it is used
func (m *Secret) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Secret) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Secret) UnmarshalBinary(b []byte) error {
var res Secret
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,67 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// ServiceAccountToken service account token
//
// swagger:model serviceAccountToken
type ServiceAccountToken struct {
// expiration seconds
ExpirationSeconds int64 `json:"expirationSeconds,omitempty"`
}
// Validate validates this service account token
func (m *ServiceAccountToken) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this service account token based on context it is used
func (m *ServiceAccountToken) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *ServiceAccountToken) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *ServiceAccountToken) UnmarshalBinary(b []byte) error {
var res ServiceAccountToken
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

85
models/state.go Normal file
View File

@@ -0,0 +1,85 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// State state
//
// swagger:model state
type State struct {
// exit code
ExitCode int64 `json:"exitCode,omitempty"`
// finished
Finished string `json:"finished,omitempty"`
// message
Message string `json:"message,omitempty"`
// reason
Reason string `json:"reason,omitempty"`
// signal
Signal int64 `json:"signal,omitempty"`
// started
Started string `json:"started,omitempty"`
// state
State string `json:"state,omitempty"`
}
// Validate validates this state
func (m *State) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this state based on context it is used
func (m *State) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *State) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *State) UnmarshalBinary(b []byte) error {
var res State
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

79
models/toleration.go Normal file
View File

@@ -0,0 +1,79 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Toleration toleration
//
// swagger:model toleration
type Toleration struct {
// effect
Effect string `json:"effect,omitempty"`
// key
Key string `json:"key,omitempty"`
// operator
Operator string `json:"operator,omitempty"`
// toleration seconds
TolerationSeconds int64 `json:"tolerationSeconds,omitempty"`
// value
Value string `json:"value,omitempty"`
}
// Validate validates this toleration
func (m *Toleration) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this toleration based on context it is used
func (m *Toleration) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *Toleration) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Toleration) UnmarshalBinary(b []byte) error {
var res Toleration
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

170
models/volume.go Normal file
View File

@@ -0,0 +1,170 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// Volume volume
//
// swagger:model volume
type Volume struct {
// name
Name string `json:"name,omitempty"`
// projected
Projected *ProjectedVolume `json:"projected,omitempty"`
// pvc
Pvc *Pvc `json:"pvc,omitempty"`
}
// Validate validates this volume
func (m *Volume) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateProjected(formats); err != nil {
res = append(res, err)
}
if err := m.validatePvc(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Volume) validateProjected(formats strfmt.Registry) error {
if swag.IsZero(m.Projected) { // not required
return nil
}
if m.Projected != nil {
if err := m.Projected.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("projected")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("projected")
}
return err
}
}
return nil
}
func (m *Volume) validatePvc(formats strfmt.Registry) error {
if swag.IsZero(m.Pvc) { // not required
return nil
}
if m.Pvc != nil {
if err := m.Pvc.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("pvc")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("pvc")
}
return err
}
}
return nil
}
// ContextValidate validate this volume based on the context it is used
func (m *Volume) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateProjected(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidatePvc(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Volume) contextValidateProjected(ctx context.Context, formats strfmt.Registry) error {
if m.Projected != nil {
if err := m.Projected.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("projected")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("projected")
}
return err
}
}
return nil
}
func (m *Volume) contextValidatePvc(ctx context.Context, formats strfmt.Registry) error {
if m.Pvc != nil {
if err := m.Pvc.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("pvc")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("pvc")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *Volume) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *Volume) UnmarshalBinary(b []byte) error {
var res Volume
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -564,3 +564,48 @@ func TestGetPodEvents(t *testing.T) {
200, resp.StatusCode, "Status Code is incorrect")
}
}
func GetPodDescribe(nameSpace string, tenant string, podName string) (*http.Response, error) {
/*
Helper function to get events for pod
URL: /namespaces/{namespace}/tenants/{tenant}/pods/{podName}/events
HTTP Verb: GET
*/
fmt.Println(nameSpace)
fmt.Println(tenant)
fmt.Println(podName)
request, err := http.NewRequest(
"GET", "http://localhost:9090/api/v1/namespaces/"+nameSpace+"/tenants/"+tenant+"/pods/"+podName+"/describe", nil)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
client := &http.Client{
Timeout: 2 * time.Second,
}
response, err := client.Do(request)
return response, err
}
func TestGetPodDescribe(t *testing.T) {
assert := assert.New(t)
namespace := "tenant-lite"
tenant := "storage-lite"
podName := "storage-lite-pool-0-0"
resp, err := GetPodDescribe(namespace, tenant, podName)
assert.Nil(err)
if err != nil {
log.Println(err)
return
}
finalResponse := inspectHTTPResponse(resp)
if resp != nil {
assert.Equal(
200, resp.StatusCode, finalResponse)
}
/*if resp != nil {
assert.Equal(
200, resp.StatusCode, "Status Code is incorrect")
}*/
}

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

@@ -208,7 +208,7 @@ func init() {
"get": {
"security": [],
"tags": [
"UserAPI"
"Auth"
],
"summary": "Returns login strategy, form or sso.",
"operationId": "LoginDetail",
@@ -232,7 +232,7 @@ func init() {
"post": {
"security": [],
"tags": [
"UserAPI"
"Auth"
],
"summary": "Identity Provider oauth2 callback endpoint.",
"operationId": "LoginOauth2Auth",
@@ -263,7 +263,7 @@ func init() {
"post": {
"security": [],
"tags": [
"UserAPI"
"Auth"
],
"summary": "Login to Operator Console.",
"operationId": "LoginOperator",
@@ -293,7 +293,7 @@ func init() {
"/logout": {
"post": {
"tags": [
"UserAPI"
"Auth"
],
"summary": "Logout from Operator.",
"operationId": "Logout",
@@ -1200,6 +1200,49 @@ func init() {
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/pods/{podName}/describe": {
"get": {
"tags": [
"OperatorAPI"
],
"summary": "Describe Pod",
"operationId": "DescribePod",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
},
{
"type": "string",
"name": "podName",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/describePodWrapper"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/pods/{podName}/events": {
"get": {
"tags": [
@@ -1665,7 +1708,7 @@ func init() {
"/session": {
"get": {
"tags": [
"UserAPI"
"Auth"
],
"summary": "Endpoint to check if your session is still valid",
"operationId": "SessionCheck",
@@ -2017,6 +2060,28 @@ func init() {
}
}
},
"condition": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"configMap": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"optional": {
"type": "boolean"
}
}
},
"configureTenantRequest": {
"type": "object",
"properties": {
@@ -2025,6 +2090,65 @@ func init() {
}
}
},
"container": {
"type": "object",
"properties": {
"args": {
"type": "array",
"items": {
"type": "string"
}
},
"containerID": {
"type": "string"
},
"environmentVariables": {
"type": "array",
"items": {
"$ref": "#/definitions/environmentVariable"
}
},
"hostPorts": {
"type": "array",
"items": {
"type": "string"
}
},
"image": {
"type": "string"
},
"imageID": {
"type": "string"
},
"lastState": {
"$ref": "#/definitions/state"
},
"mounts": {
"type": "array",
"items": {
"$ref": "#/definitions/mount"
}
},
"name": {
"type": "string"
},
"ports": {
"type": "array",
"items": {
"type": "string"
}
},
"ready": {
"type": "boolean"
},
"restartCount": {
"type": "integer"
},
"state": {
"$ref": "#/definitions/state"
}
}
},
"createTenantRequest": {
"type": "object",
"required": [
@@ -2157,6 +2281,95 @@ func init() {
}
}
},
"describePodWrapper": {
"type": "object",
"properties": {
"annotations": {
"type": "array",
"items": {
"$ref": "#/definitions/annotation"
}
},
"conditions": {
"type": "array",
"items": {
"$ref": "#/definitions/condition"
}
},
"containers": {
"type": "array",
"items": {
"$ref": "#/definitions/container"
}
},
"controllerRef": {
"type": "string"
},
"deletionGracePeriodSeconds": {
"type": "integer"
},
"deletionTimestamp": {
"type": "string"
},
"labels": {
"type": "array",
"items": {
"$ref": "#/definitions/label"
}
},
"message": {
"type": "string"
},
"name": {
"type": "string"
},
"namespace": {
"type": "string"
},
"nodeName": {
"type": "string"
},
"nodeSelector": {
"type": "array",
"items": {
"$ref": "#/definitions/nodeSelector"
}
},
"phase": {
"type": "string"
},
"podIP": {
"type": "string"
},
"priority": {
"type": "integer"
},
"priorityClassName": {
"type": "string"
},
"qosClass": {
"type": "string"
},
"reason": {
"type": "string"
},
"startTime": {
"type": "string"
},
"tolerations": {
"type": "array",
"items": {
"$ref": "#/definitions/toleration"
}
},
"volumes": {
"type": "array",
"items": {
"$ref": "#/definitions/volume"
}
}
}
},
"directCSIDriveInfo": {
"type": "object",
"properties": {
@@ -2324,6 +2537,17 @@ func init() {
}
]
},
"environmentVariable": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"error": {
"type": "object",
"required": [
@@ -2886,6 +3110,23 @@ func init() {
}
}
},
"mount": {
"type": "object",
"properties": {
"mountPath": {
"type": "string"
},
"name": {
"type": "string"
},
"readOnly": {
"type": "boolean"
},
"subPath": {
"type": "string"
}
}
},
"namespace": {
"type": "object",
"required": [
@@ -3357,6 +3598,34 @@ func init() {
}
}
},
"projectedVolume": {
"type": "object",
"properties": {
"sources": {
"type": "array",
"items": {
"$ref": "#/definitions/projectedVolumeSource"
}
}
}
},
"projectedVolumeSource": {
"type": "object",
"properties": {
"configMap": {
"$ref": "#/definitions/configMap"
},
"downwardApi": {
"type": "boolean"
},
"secret": {
"$ref": "#/definitions/secret"
},
"serviceAccountToken": {
"$ref": "#/definitions/serviceAccountToken"
}
}
},
"prometheusConfiguration": {
"type": "object",
"properties": {
@@ -3383,6 +3652,17 @@ func init() {
}
}
},
"pvc": {
"type": "object",
"properties": {
"claimName": {
"type": "string"
},
"readOnly": {
"type": "boolean"
}
}
},
"pvcsListResponse": {
"type": "object",
"properties": {
@@ -3442,6 +3722,17 @@ func init() {
}
}
},
"secret": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"optional": {
"type": "boolean"
}
}
},
"securityContext": {
"type": "object",
"required": [
@@ -3465,6 +3756,40 @@ func init() {
}
}
},
"serviceAccountToken": {
"type": "object",
"properties": {
"expirationSeconds": {
"type": "integer"
}
}
},
"state": {
"type": "object",
"properties": {
"exitCode": {
"type": "integer"
},
"finished": {
"type": "string"
},
"message": {
"type": "string"
},
"reason": {
"type": "string"
},
"signal": {
"type": "integer"
},
"started": {
"type": "string"
},
"state": {
"type": "string"
}
}
},
"subscriptionValidateRequest": {
"type": "object",
"properties": {
@@ -3901,6 +4226,26 @@ func init() {
}
}
},
"toleration": {
"type": "object",
"properties": {
"effect": {
"type": "string"
},
"key": {
"type": "string"
},
"operator": {
"type": "string"
},
"tolerationSeconds": {
"type": "integer"
},
"value": {
"type": "string"
}
}
},
"updateDomainsRequest": {
"type": "object",
"properties": {
@@ -4088,6 +4433,20 @@ func init() {
}
}
}
},
"volume": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"projected": {
"$ref": "#/definitions/projectedVolume"
},
"pvc": {
"$ref": "#/definitions/pvc"
}
}
}
},
"securityDefinitions": {
@@ -4278,7 +4637,7 @@ func init() {
"get": {
"security": [],
"tags": [
"UserAPI"
"Auth"
],
"summary": "Returns login strategy, form or sso.",
"operationId": "LoginDetail",
@@ -4302,7 +4661,7 @@ func init() {
"post": {
"security": [],
"tags": [
"UserAPI"
"Auth"
],
"summary": "Identity Provider oauth2 callback endpoint.",
"operationId": "LoginOauth2Auth",
@@ -4333,7 +4692,7 @@ func init() {
"post": {
"security": [],
"tags": [
"UserAPI"
"Auth"
],
"summary": "Login to Operator Console.",
"operationId": "LoginOperator",
@@ -4363,7 +4722,7 @@ func init() {
"/logout": {
"post": {
"tags": [
"UserAPI"
"Auth"
],
"summary": "Logout from Operator.",
"operationId": "Logout",
@@ -5270,6 +5629,49 @@ func init() {
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/pods/{podName}/describe": {
"get": {
"tags": [
"OperatorAPI"
],
"summary": "Describe Pod",
"operationId": "DescribePod",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
},
{
"type": "string",
"name": "podName",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/describePodWrapper"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/pods/{podName}/events": {
"get": {
"tags": [
@@ -5735,7 +6137,7 @@ func init() {
"/session": {
"get": {
"tags": [
"UserAPI"
"Auth"
],
"summary": "Endpoint to check if your session is still valid",
"operationId": "SessionCheck",
@@ -6930,6 +7332,28 @@ func init() {
}
}
},
"condition": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"configMap": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"optional": {
"type": "boolean"
}
}
},
"configureTenantRequest": {
"type": "object",
"properties": {
@@ -6938,6 +7362,65 @@ func init() {
}
}
},
"container": {
"type": "object",
"properties": {
"args": {
"type": "array",
"items": {
"type": "string"
}
},
"containerID": {
"type": "string"
},
"environmentVariables": {
"type": "array",
"items": {
"$ref": "#/definitions/environmentVariable"
}
},
"hostPorts": {
"type": "array",
"items": {
"type": "string"
}
},
"image": {
"type": "string"
},
"imageID": {
"type": "string"
},
"lastState": {
"$ref": "#/definitions/state"
},
"mounts": {
"type": "array",
"items": {
"$ref": "#/definitions/mount"
}
},
"name": {
"type": "string"
},
"ports": {
"type": "array",
"items": {
"type": "string"
}
},
"ready": {
"type": "boolean"
},
"restartCount": {
"type": "integer"
},
"state": {
"$ref": "#/definitions/state"
}
}
},
"createTenantRequest": {
"type": "object",
"required": [
@@ -7070,6 +7553,95 @@ func init() {
}
}
},
"describePodWrapper": {
"type": "object",
"properties": {
"annotations": {
"type": "array",
"items": {
"$ref": "#/definitions/annotation"
}
},
"conditions": {
"type": "array",
"items": {
"$ref": "#/definitions/condition"
}
},
"containers": {
"type": "array",
"items": {
"$ref": "#/definitions/container"
}
},
"controllerRef": {
"type": "string"
},
"deletionGracePeriodSeconds": {
"type": "integer"
},
"deletionTimestamp": {
"type": "string"
},
"labels": {
"type": "array",
"items": {
"$ref": "#/definitions/label"
}
},
"message": {
"type": "string"
},
"name": {
"type": "string"
},
"namespace": {
"type": "string"
},
"nodeName": {
"type": "string"
},
"nodeSelector": {
"type": "array",
"items": {
"$ref": "#/definitions/nodeSelector"
}
},
"phase": {
"type": "string"
},
"podIP": {
"type": "string"
},
"priority": {
"type": "integer"
},
"priorityClassName": {
"type": "string"
},
"qosClass": {
"type": "string"
},
"reason": {
"type": "string"
},
"startTime": {
"type": "string"
},
"tolerations": {
"type": "array",
"items": {
"$ref": "#/definitions/toleration"
}
},
"volumes": {
"type": "array",
"items": {
"$ref": "#/definitions/volume"
}
}
}
},
"directCSIDriveInfo": {
"type": "object",
"properties": {
@@ -7237,6 +7809,17 @@ func init() {
}
]
},
"environmentVariable": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"error": {
"type": "object",
"required": [
@@ -7787,6 +8370,23 @@ func init() {
}
}
},
"mount": {
"type": "object",
"properties": {
"mountPath": {
"type": "string"
},
"name": {
"type": "string"
},
"readOnly": {
"type": "boolean"
},
"subPath": {
"type": "string"
}
}
},
"namespace": {
"type": "object",
"required": [
@@ -8123,6 +8723,34 @@ func init() {
}
}
},
"projectedVolume": {
"type": "object",
"properties": {
"sources": {
"type": "array",
"items": {
"$ref": "#/definitions/projectedVolumeSource"
}
}
}
},
"projectedVolumeSource": {
"type": "object",
"properties": {
"configMap": {
"$ref": "#/definitions/configMap"
},
"downwardApi": {
"type": "boolean"
},
"secret": {
"$ref": "#/definitions/secret"
},
"serviceAccountToken": {
"$ref": "#/definitions/serviceAccountToken"
}
}
},
"prometheusConfiguration": {
"type": "object",
"properties": {
@@ -8149,6 +8777,17 @@ func init() {
}
}
},
"pvc": {
"type": "object",
"properties": {
"claimName": {
"type": "string"
},
"readOnly": {
"type": "boolean"
}
}
},
"pvcsListResponse": {
"type": "object",
"properties": {
@@ -8208,6 +8847,17 @@ func init() {
}
}
},
"secret": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"optional": {
"type": "boolean"
}
}
},
"securityContext": {
"type": "object",
"required": [
@@ -8231,6 +8881,40 @@ func init() {
}
}
},
"serviceAccountToken": {
"type": "object",
"properties": {
"expirationSeconds": {
"type": "integer"
}
}
},
"state": {
"type": "object",
"properties": {
"exitCode": {
"type": "integer"
},
"finished": {
"type": "string"
},
"message": {
"type": "string"
},
"reason": {
"type": "string"
},
"signal": {
"type": "integer"
},
"started": {
"type": "string"
},
"state": {
"type": "string"
}
}
},
"subscriptionValidateRequest": {
"type": "object",
"properties": {
@@ -8667,6 +9351,26 @@ func init() {
}
}
},
"toleration": {
"type": "object",
"properties": {
"effect": {
"type": "string"
},
"key": {
"type": "string"
},
"operator": {
"type": "string"
},
"tolerationSeconds": {
"type": "integer"
},
"value": {
"type": "string"
}
}
},
"updateDomainsRequest": {
"type": "object",
"properties": {
@@ -8854,6 +9558,20 @@ func init() {
}
}
}
},
"volume": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"projected": {
"$ref": "#/definitions/projectedVolume"
},
"pvc": {
"$ref": "#/definitions/pvc"
}
}
}
},
"securityDefinitions": {

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

@@ -33,44 +33,45 @@ import (
"github.com/minio/console/models"
opauth "github.com/minio/console/operatorapi/auth"
"github.com/minio/console/operatorapi/operations"
"github.com/minio/console/operatorapi/operations/user_api"
authApi "github.com/minio/console/operatorapi/operations/auth"
"github.com/minio/console/pkg/auth"
"github.com/minio/console/pkg/auth/idp/oauth2"
)
func registerLoginHandlers(api *operations.OperatorAPI) {
// GET login strategy
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse(params.HTTPRequest)
api.AuthLoginDetailHandler = authApi.LoginDetailHandlerFunc(func(params authApi.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse(params)
if err != nil {
return user_api.NewLoginDetailDefault(int(err.Code)).WithPayload(err)
return authApi.NewLoginDetailDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewLoginDetailOK().WithPayload(loginDetails)
return authApi.NewLoginDetailOK().WithPayload(loginDetails)
})
// POST login using k8s service account token
api.UserAPILoginOperatorHandler = user_api.LoginOperatorHandlerFunc(func(params user_api.LoginOperatorParams) middleware.Responder {
loginResponse, err := getLoginOperatorResponse(params.Body)
api.AuthLoginOperatorHandler = authApi.LoginOperatorHandlerFunc(func(params authApi.LoginOperatorParams) middleware.Responder {
loginResponse, err := getLoginOperatorResponse(params)
if err != nil {
return user_api.NewLoginOperatorDefault(int(err.Code)).WithPayload(err)
return authApi.NewLoginOperatorDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOperatorNoContent().WriteResponse(w, p)
authApi.NewLoginOperatorNoContent().WriteResponse(w, p)
})
})
// POST login using external IDP
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.HTTPRequest, params.Body)
api.AuthLoginOauth2AuthHandler = authApi.LoginOauth2AuthHandlerFunc(func(params authApi.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params)
if err != nil {
return user_api.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
return authApi.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
// Custom response writer to set the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
http.SetCookie(w, &cookie)
user_api.NewLoginOauth2AuthNoContent().WriteResponse(w, p)
authApi.NewLoginOauth2AuthNoContent().WriteResponse(w, p)
})
})
}
@@ -86,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 := ""
@@ -102,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}
@@ -125,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{
@@ -157,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) {
@@ -169,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

@@ -23,20 +23,20 @@ import (
"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"
authApi "github.com/minio/console/operatorapi/operations/auth"
"github.com/minio/console/restapi"
)
func registerLogoutHandlers(api *operations.OperatorAPI) {
// logout from console
api.UserAPILogoutHandler = user_api.LogoutHandlerFunc(func(params user_api.LogoutParams, session *models.Principal) middleware.Responder {
api.AuthLogoutHandler = authApi.LogoutHandlerFunc(func(params authApi.LogoutParams, session *models.Principal) middleware.Responder {
// Custom response writer to expire the session cookies
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
expiredCookie := restapi.ExpireSessionCookie()
// this will tell the browser to clear the cookie and invalidate user session
// additionally we are deleting the cookie from the client side
http.SetCookie(w, &expiredCookie)
user_api.NewLogoutOK().WriteResponse(w, p)
authApi.NewLogoutOK().WriteResponse(w, p)
})
})
}

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,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
@@ -46,7 +46,7 @@ func NewLoginDetail(ctx *middleware.Context, handler LoginDetailHandler) *LoginD
return &LoginDetail{Context: ctx, Handler: handler}
}
/* LoginDetail swagger:route GET /login UserAPI loginDetail
/* LoginDetail swagger:route GET /login Auth loginDetail
Returns login strategy, form or sso.

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
@@ -46,7 +46,7 @@ func NewLoginOauth2Auth(ctx *middleware.Context, handler LoginOauth2AuthHandler)
return &LoginOauth2Auth{Context: ctx, Handler: handler}
}
/* LoginOauth2Auth swagger:route POST /login/oauth2/auth UserAPI loginOauth2Auth
/* LoginOauth2Auth swagger:route POST /login/oauth2/auth Auth loginOauth2Auth
Identity Provider oauth2 callback endpoint.

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
@@ -46,7 +46,7 @@ func NewLoginOperator(ctx *middleware.Context, handler LoginOperatorHandler) *Lo
return &LoginOperator{Context: ctx, Handler: handler}
}
/* LoginOperator swagger:route POST /login/operator UserAPI loginOperator
/* LoginOperator swagger:route POST /login/operator Auth loginOperator
Login to Operator Console.

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
@@ -48,7 +48,7 @@ func NewLogout(ctx *middleware.Context, handler LogoutHandler) *Logout {
return &Logout{Context: ctx, Handler: handler}
}
/* Logout swagger:route POST /logout UserAPI logout
/* Logout swagger:route POST /logout Auth logout
Logout from Operator.

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
@@ -48,7 +48,7 @@ func NewSessionCheck(ctx *middleware.Context, handler SessionCheckHandler) *Sess
return &SessionCheck{Context: ctx, Handler: handler}
}
/* SessionCheck swagger:route GET /session UserAPI sessionCheck
/* SessionCheck swagger:route GET /session Auth sessionCheck
Endpoint to check if your session is still valid

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package user_api
package auth
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command

View File

@@ -37,6 +37,7 @@ import (
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations/auth"
"github.com/minio/console/operatorapi/operations/operator_api"
"github.com/minio/console/operatorapi/operations/user_api"
)
@@ -81,6 +82,9 @@ func NewOperatorAPI(spec *loads.Document) *OperatorAPI {
OperatorAPIDeleteTenantHandler: operator_api.DeleteTenantHandlerFunc(func(params operator_api.DeleteTenantParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.DeleteTenant has not yet been implemented")
}),
OperatorAPIDescribePodHandler: operator_api.DescribePodHandlerFunc(func(params operator_api.DescribePodParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.DescribePod has not yet been implemented")
}),
OperatorAPIDisableTenantLoggingHandler: operator_api.DisableTenantLoggingHandlerFunc(func(params operator_api.DisableTenantLoggingParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.DisableTenantLogging has not yet been implemented")
}),
@@ -141,23 +145,23 @@ func NewOperatorAPI(spec *loads.Document) *OperatorAPI {
OperatorAPIListTenantsHandler: operator_api.ListTenantsHandlerFunc(func(params operator_api.ListTenantsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.ListTenants has not yet been implemented")
}),
UserAPILoginDetailHandler: user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginDetail has not yet been implemented")
AuthLoginDetailHandler: auth.LoginDetailHandlerFunc(func(params auth.LoginDetailParams) middleware.Responder {
return middleware.NotImplemented("operation auth.LoginDetail has not yet been implemented")
}),
UserAPILoginOauth2AuthHandler: user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginOauth2Auth has not yet been implemented")
AuthLoginOauth2AuthHandler: auth.LoginOauth2AuthHandlerFunc(func(params auth.LoginOauth2AuthParams) middleware.Responder {
return middleware.NotImplemented("operation auth.LoginOauth2Auth has not yet been implemented")
}),
UserAPILoginOperatorHandler: user_api.LoginOperatorHandlerFunc(func(params user_api.LoginOperatorParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginOperator has not yet been implemented")
AuthLoginOperatorHandler: auth.LoginOperatorHandlerFunc(func(params auth.LoginOperatorParams) middleware.Responder {
return middleware.NotImplemented("operation auth.LoginOperator has not yet been implemented")
}),
UserAPILogoutHandler: user_api.LogoutHandlerFunc(func(params user_api.LogoutParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.Logout has not yet been implemented")
AuthLogoutHandler: auth.LogoutHandlerFunc(func(params auth.LogoutParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation auth.Logout has not yet been implemented")
}),
OperatorAPIPutTenantYAMLHandler: operator_api.PutTenantYAMLHandlerFunc(func(params operator_api.PutTenantYAMLParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.PutTenantYAML has not yet been implemented")
}),
UserAPISessionCheckHandler: user_api.SessionCheckHandlerFunc(func(params user_api.SessionCheckParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation user_api.SessionCheck has not yet been implemented")
AuthSessionCheckHandler: auth.SessionCheckHandlerFunc(func(params auth.SessionCheckParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation auth.SessionCheck has not yet been implemented")
}),
OperatorAPISetTenantLogsHandler: operator_api.SetTenantLogsHandlerFunc(func(params operator_api.SetTenantLogsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.SetTenantLogs has not yet been implemented")
@@ -277,6 +281,8 @@ type OperatorAPI struct {
OperatorAPIDeletePodHandler operator_api.DeletePodHandler
// OperatorAPIDeleteTenantHandler sets the operation handler for the delete tenant operation
OperatorAPIDeleteTenantHandler operator_api.DeleteTenantHandler
// OperatorAPIDescribePodHandler sets the operation handler for the describe pod operation
OperatorAPIDescribePodHandler operator_api.DescribePodHandler
// OperatorAPIDisableTenantLoggingHandler sets the operation handler for the disable tenant logging operation
OperatorAPIDisableTenantLoggingHandler operator_api.DisableTenantLoggingHandler
// OperatorAPIEnableTenantLoggingHandler sets the operation handler for the enable tenant logging operation
@@ -317,18 +323,18 @@ type OperatorAPI struct {
OperatorAPIListPVCsForTenantHandler operator_api.ListPVCsForTenantHandler
// OperatorAPIListTenantsHandler sets the operation handler for the list tenants operation
OperatorAPIListTenantsHandler operator_api.ListTenantsHandler
// UserAPILoginDetailHandler sets the operation handler for the login detail operation
UserAPILoginDetailHandler user_api.LoginDetailHandler
// UserAPILoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
UserAPILoginOauth2AuthHandler user_api.LoginOauth2AuthHandler
// UserAPILoginOperatorHandler sets the operation handler for the login operator operation
UserAPILoginOperatorHandler user_api.LoginOperatorHandler
// UserAPILogoutHandler sets the operation handler for the logout operation
UserAPILogoutHandler user_api.LogoutHandler
// AuthLoginDetailHandler sets the operation handler for the login detail operation
AuthLoginDetailHandler auth.LoginDetailHandler
// AuthLoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
AuthLoginOauth2AuthHandler auth.LoginOauth2AuthHandler
// AuthLoginOperatorHandler sets the operation handler for the login operator operation
AuthLoginOperatorHandler auth.LoginOperatorHandler
// AuthLogoutHandler sets the operation handler for the logout operation
AuthLogoutHandler auth.LogoutHandler
// OperatorAPIPutTenantYAMLHandler sets the operation handler for the put tenant y a m l operation
OperatorAPIPutTenantYAMLHandler operator_api.PutTenantYAMLHandler
// UserAPISessionCheckHandler sets the operation handler for the session check operation
UserAPISessionCheckHandler user_api.SessionCheckHandler
// AuthSessionCheckHandler sets the operation handler for the session check operation
AuthSessionCheckHandler auth.SessionCheckHandler
// OperatorAPISetTenantLogsHandler sets the operation handler for the set tenant logs operation
OperatorAPISetTenantLogsHandler operator_api.SetTenantLogsHandler
// OperatorAPISetTenantMonitoringHandler sets the operation handler for the set tenant monitoring operation
@@ -466,6 +472,9 @@ func (o *OperatorAPI) Validate() error {
if o.OperatorAPIDeleteTenantHandler == nil {
unregistered = append(unregistered, "operator_api.DeleteTenantHandler")
}
if o.OperatorAPIDescribePodHandler == nil {
unregistered = append(unregistered, "operator_api.DescribePodHandler")
}
if o.OperatorAPIDisableTenantLoggingHandler == nil {
unregistered = append(unregistered, "operator_api.DisableTenantLoggingHandler")
}
@@ -526,23 +535,23 @@ func (o *OperatorAPI) Validate() error {
if o.OperatorAPIListTenantsHandler == nil {
unregistered = append(unregistered, "operator_api.ListTenantsHandler")
}
if o.UserAPILoginDetailHandler == nil {
unregistered = append(unregistered, "user_api.LoginDetailHandler")
if o.AuthLoginDetailHandler == nil {
unregistered = append(unregistered, "auth.LoginDetailHandler")
}
if o.UserAPILoginOauth2AuthHandler == nil {
unregistered = append(unregistered, "user_api.LoginOauth2AuthHandler")
if o.AuthLoginOauth2AuthHandler == nil {
unregistered = append(unregistered, "auth.LoginOauth2AuthHandler")
}
if o.UserAPILoginOperatorHandler == nil {
unregistered = append(unregistered, "user_api.LoginOperatorHandler")
if o.AuthLoginOperatorHandler == nil {
unregistered = append(unregistered, "auth.LoginOperatorHandler")
}
if o.UserAPILogoutHandler == nil {
unregistered = append(unregistered, "user_api.LogoutHandler")
if o.AuthLogoutHandler == nil {
unregistered = append(unregistered, "auth.LogoutHandler")
}
if o.OperatorAPIPutTenantYAMLHandler == nil {
unregistered = append(unregistered, "operator_api.PutTenantYAMLHandler")
}
if o.UserAPISessionCheckHandler == nil {
unregistered = append(unregistered, "user_api.SessionCheckHandler")
if o.AuthSessionCheckHandler == nil {
unregistered = append(unregistered, "auth.SessionCheckHandler")
}
if o.OperatorAPISetTenantLogsHandler == nil {
unregistered = append(unregistered, "operator_api.SetTenantLogsHandler")
@@ -723,6 +732,10 @@ func (o *OperatorAPI) initHandlerCache() {
o.handlers["DELETE"] = make(map[string]http.Handler)
}
o.handlers["DELETE"]["/namespaces/{namespace}/tenants/{tenant}"] = operator_api.NewDeleteTenant(o.context, o.OperatorAPIDeleteTenantHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}/pods/{podName}/describe"] = operator_api.NewDescribePod(o.context, o.OperatorAPIDescribePodHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
@@ -806,19 +819,19 @@ func (o *OperatorAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/login"] = user_api.NewLoginDetail(o.context, o.UserAPILoginDetailHandler)
o.handlers["GET"]["/login"] = auth.NewLoginDetail(o.context, o.AuthLoginDetailHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/oauth2/auth"] = user_api.NewLoginOauth2Auth(o.context, o.UserAPILoginOauth2AuthHandler)
o.handlers["POST"]["/login/oauth2/auth"] = auth.NewLoginOauth2Auth(o.context, o.AuthLoginOauth2AuthHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/operator"] = user_api.NewLoginOperator(o.context, o.UserAPILoginOperatorHandler)
o.handlers["POST"]["/login/operator"] = auth.NewLoginOperator(o.context, o.AuthLoginOperatorHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/logout"] = user_api.NewLogout(o.context, o.UserAPILogoutHandler)
o.handlers["POST"]["/logout"] = auth.NewLogout(o.context, o.AuthLogoutHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
@@ -826,7 +839,7 @@ func (o *OperatorAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/session"] = user_api.NewSessionCheck(o.context, o.UserAPISessionCheckHandler)
o.handlers["GET"]["/session"] = auth.NewSessionCheck(o.context, o.AuthSessionCheckHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// DescribePodHandlerFunc turns a function with the right signature into a describe pod handler
type DescribePodHandlerFunc func(DescribePodParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn DescribePodHandlerFunc) Handle(params DescribePodParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// DescribePodHandler interface for that can handle valid describe pod params
type DescribePodHandler interface {
Handle(DescribePodParams, *models.Principal) middleware.Responder
}
// NewDescribePod creates a new http.Handler for the describe pod operation
func NewDescribePod(ctx *middleware.Context, handler DescribePodHandler) *DescribePod {
return &DescribePod{Context: ctx, Handler: handler}
}
/* DescribePod swagger:route GET /namespaces/{namespace}/tenants/{tenant}/pods/{podName}/describe OperatorAPI describePod
Describe Pod
*/
type DescribePod struct {
Context *middleware.Context
Handler DescribePodHandler
}
func (o *DescribePod) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewDescribePodParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,136 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
)
// NewDescribePodParams creates a new DescribePodParams object
//
// There are no default values defined in the spec.
func NewDescribePodParams() DescribePodParams {
return DescribePodParams{}
}
// DescribePodParams contains all the bound params for the describe pod operation
// typically these are obtained from a http.Request
//
// swagger:parameters DescribePod
type DescribePodParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
PodName string
/*
Required: true
In: path
*/
Tenant string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewDescribePodParams() beforehand.
func (o *DescribePodParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rPodName, rhkPodName, _ := route.Params.GetOK("podName")
if err := o.bindPodName(rPodName, rhkPodName, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *DescribePodParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Namespace = raw
return nil
}
// bindPodName binds and validates parameter PodName from path.
func (o *DescribePodParams) bindPodName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.PodName = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *DescribePodParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Tenant = raw
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// DescribePodOKCode is the HTTP code returned for type DescribePodOK
const DescribePodOKCode int = 200
/*DescribePodOK A successful response.
swagger:response describePodOK
*/
type DescribePodOK struct {
/*
In: Body
*/
Payload *models.DescribePodWrapper `json:"body,omitempty"`
}
// NewDescribePodOK creates DescribePodOK with default headers values
func NewDescribePodOK() *DescribePodOK {
return &DescribePodOK{}
}
// WithPayload adds the payload to the describe pod o k response
func (o *DescribePodOK) WithPayload(payload *models.DescribePodWrapper) *DescribePodOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the describe pod o k response
func (o *DescribePodOK) SetPayload(payload *models.DescribePodWrapper) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DescribePodOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*DescribePodDefault Generic error response.
swagger:response describePodDefault
*/
type DescribePodDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewDescribePodDefault creates DescribePodDefault with default headers values
func NewDescribePodDefault(code int) *DescribePodDefault {
if code <= 0 {
code = 500
}
return &DescribePodDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the describe pod default response
func (o *DescribePodDefault) WithStatusCode(code int) *DescribePodDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the describe pod default response
func (o *DescribePodDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the describe pod default response
func (o *DescribePodDefault) WithPayload(payload *models.Error) *DescribePodDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the describe pod default response
func (o *DescribePodDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *DescribePodDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,132 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// DescribePodURL generates an URL for the describe pod operation
type DescribePodURL struct {
Namespace string
PodName string
Tenant string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DescribePodURL) WithBasePath(bp string) *DescribePodURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *DescribePodURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *DescribePodURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/pods/{podName}/describe"
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on DescribePodURL")
}
podName := o.PodName
if podName != "" {
_path = strings.Replace(_path, "{podName}", podName, -1)
} else {
return nil, errors.New("podName is required on DescribePodURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on DescribePodURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *DescribePodURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *DescribePodURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *DescribePodURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on DescribePodURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on DescribePodURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *DescribePodURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

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,30 +17,35 @@
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"
"github.com/minio/console/operatorapi/operations/user_api"
authApi "github.com/minio/console/operatorapi/operations/auth"
)
func registerSessionHandlers(api *operations.OperatorAPI) {
// session check
api.UserAPISessionCheckHandler = user_api.SessionCheckHandlerFunc(func(params user_api.SessionCheckParams, session *models.Principal) middleware.Responder {
sessionResp, err := getSessionResponse(session)
api.AuthSessionCheckHandler = authApi.SessionCheckHandlerFunc(func(params authApi.SessionCheckParams, session *models.Principal) middleware.Responder {
sessionResp, err := getSessionResponse(session, params)
if err != nil {
return user_api.NewSessionCheckDefault(int(err.Code)).WithPayload(err)
return authApi.NewSessionCheckDefault(int(err.Code)).WithPayload(err)
}
return user_api.NewSessionCheckOK().WithPayload(sessionResp)
return authApi.NewSessionCheckOK().WithPayload(sessionResp)
})
}
// 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

File diff suppressed because it is too large Load Diff

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")
}
}

Some files were not shown because too many files have changed in this diff Show More